package io.gitee.thant.utils;

import java.io.Serializable;
import java.sql.Clob;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.gitee.thant.common.map.SQLMap;
import io.gitee.thant.sqlgear.SQLRunner;

public class JsonBean implements Map<String, Object>, Serializable {
	private static final long serialVersionUID = 1L;
	
	public static final String sysmark = "<*sys*>";
	private static final String ignore = "<*ignore*>";

	public enum dataState {
		New,
		NotModify,
		Modified
	}
	
	private ObjectMapper _om = new ObjectMapper();
	private Map<String, Object> _map = new LinkedHashMap<String, Object>();
	//private String storeType = "mysql";
	
	private void init() {
		_om.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
		_om.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
		_om.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
		_om.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
	}

	public JsonBean() {
		init();
	}
	
	public JsonBean(String s) {
		init();
		if (StringUtil.isEmpty(s)) return;

		try {
			String s1 = s.replaceAll("/\\*[^\\*/]+\\*/", ""); //去掉/**/注释 暂不支持嵌套
			if (!StringUtil.isEmpty(s1)) {
				setNode(_om.readTree(s1));
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/*public JsonBean(JsonNode node) {
		init();
		setNode(node);
	}*/

	public JsonBean(Map<String, Object> map) {
		init();
		_map = map;
	}

	public <T> JsonBean(T[] lst) {
		init();
		toMap(lst, _map);
	}

	private JsonBean setNode(JsonNode node) {
		_map.clear();
		toMap(node, _map);
		return this;
	}
	
	@Override
	public String toString() {
		try {
			return _om.writeValueAsString(_map);
		} catch (Exception e) {
			LogHelper.error(e);
			return null;
		}
	}
	
	public JsonBean clone() {
		JsonBean newone = new JsonBean(toString());
		return newone;
	}
	
	@SuppressWarnings("unchecked")
	private static void clearSysInfo(Map<String, Object> map) {
		map.remove(sysmark);
		Iterator<Entry<String, Object>> it = map.entrySet().iterator();
		while (it.hasNext()) {
			Object val = it.next().getValue();
			if (val instanceof Map) {
				clearSysInfo((Map<String, Object>)val);
			}
		}
	}
	
	public String toDataString() {
		JsonBean newone = clone();
		clearSysInfo(newone._map);
		try {
			return _om.writeValueAsString(newone);
		} catch (Exception e) {
			LogHelper.error(e);
			return null;
		}
	}
	
	/*public Map<String, Object> getMap() {
		return _map;
	}*/
	
	private static Object toValue(JsonNode val) {
		if (val.isTextual()) {
			return val.asText();
		} else if (val.isInt()) {
			return val.asInt();
		} else if (val.isLong()) {
			return val.asLong();
		} else if (val.isFloat() || val.isDouble()) {
			return val.asDouble();
		} else if (val.isBoolean()) {
			return val.asBoolean();
		} else if (val.isNull()) {
			return null;
		} else {
			return val;
		}
	}
	
	public static Map<String, Object> toMap(JsonNode node, Map<String, Object> map) {
		Object val;
		Map<String, Object> cmap;
	    if (node.isObject())
	    {
	        Iterator<Entry<String, JsonNode>> it = node.fields();
	        while (it.hasNext())
	        {
	            Entry<String, JsonNode> entry = it.next();
	            if (entry.getValue().isValueNode()) {
	            	val = toValue(entry.getValue());
	            } else {
	            	cmap = new LinkedHashMap<String, Object>();
	            	val = toMap(entry.getValue(), cmap);
	            }
            	map.put(entry.getKey(), val);
	        }
	    } else if (node.isArray()) {
	    	int no = 0;
	        Iterator<JsonNode> it = node.iterator();
	        while (it.hasNext())
	        {
	            JsonNode ele = it.next();
	            if (ele.isValueNode()) {
	            	val = toValue(ele);
	            } else {
	            	cmap = new LinkedHashMap<String, Object>();
	            	val = toMap(ele, cmap);
	            }
	            map.put(String.valueOf(no++), val);
	        }
	    }
	    return map;
	}

	public static <T> Map<String, Object> toMap(T[] node, Map<String, Object> map) {
		for (int i=0; i<node.length; ++i) {
			map.put(String.valueOf(i), node[i]);
		}
		return map;
	}

	/*
	 * 直接读JsonNode
	 * /
	public JsonNode get(String path) {
		int begin, no;
		JsonNode nd = node;
		String[] pathA = path.split("\\.");
		for (int i=0; nd != null && i<pathA.length; ++i) {
			if (pathA[i].charAt(pathA[i].length()-1) == ']' && (begin = pathA[i].indexOf('['))>0) {
				nd = nd.get(pathA[i].substring(0, begin));
				if (null == nd) return null;
				
				no = Integer.parseInt(pathA[i].substring(begin+1, pathA[i].length()-1));
				if (nd.isArray() && no>=0 && no<nd.size()) {
					nd = nd.get(no);
				} else {
					return null;
				}
			} else {
				nd = nd.get(pathA[i]);
			}
		}
		return nd;
	}*/
	
	@SuppressWarnings("unchecked")
	public Object getPath(String path) {
		Object nd = _map;
		if (!StringUtil.isEmpty(path)) {
			Map<String, Object> map;
			String[] pathA = StringUtil.split(path, ".");//path.split("[\\[\\.]");
			for (int i=0; nd instanceof Map && i<pathA.length; ++i) {
				if (pathA[i].endsWith("]")) {
					pathA[i] = pathA[i].substring(0, pathA[i].length()-1);
				}
				
				map = (Map<String, Object>)nd;
				nd = map.get(pathA[i]);
			}
		}
		return nd;
	}
	
	@SuppressWarnings("unchecked")
	private Map<String, Object> getParentMap(String path, StringBuilder last) {
		Object nd = _map;
		Map<String, Object> map;
		String[] pathA = StringUtil.split(path, ".");//path.split("[\\[\\.]");
		for (int i=0; i<pathA.length-1; ++i) {
			if (pathA[i].endsWith("]")) {
				pathA[i] = pathA[i].substring(0, pathA[i].length()-1);
			}
			
			map = (Map<String, Object>)nd;
			nd = map.get(pathA[i]);
			if (null == nd || !(nd instanceof Map)) {
				return null;
			}
		}
		last.setLength(0);
		last.append(pathA[pathA.length-1]);
		return (Map<String, Object>)nd;
	}
	
	public int size() {
		return _map.size();
	}
	
	@SuppressWarnings("unchecked")
	public JsonBean getBean(String path) {
		Object nd = getPath(path);
		if (nd instanceof Map) {
			return new JsonBean((Map<String, Object>)nd);
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	public Iterator<Entry<String, Object>> getIterator(String path) {
		Object nd = getPath(path);
		if (nd instanceof Map) {
			return ((Map<String, Object>)nd).entrySet().iterator();
		}
		return null;
	}

	public int getInt(String path, int defaultvalue) {
		Object nd = getPath(path);
		if (nd instanceof JsonNode) {
			JsonNode node = (JsonNode)nd;
			if (node.isInt()) { 
				return node.asInt();
			}
		} else if (nd instanceof Integer) {
			return (Integer)nd;
		} else if (nd instanceof Long) {
			return (int)(long)(Long)nd;
		}

		try {
			return Integer.valueOf(nd.toString());
		} catch (Exception e) {
			return defaultvalue;
		}
	}
	
	public long getLong(String path, long defaultvalue) {
		Object nd = getPath(path);
		if (nd instanceof JsonNode) {
			JsonNode node = (JsonNode)nd;
			if (node.isLong()) { 
				return node.asLong();
			}
		} else if (nd instanceof Integer) {
			return (Integer)nd;
		} else if (nd instanceof Long) {
			return (Long)nd;
		}

		try {
			return Long.valueOf(nd.toString());
		} catch (Exception e) {
			return defaultvalue;
		}
	}
	
	public double getDouble(String path, double defaultvalue) {
		Object nd = getPath(path);
		if (nd instanceof JsonNode) {
			JsonNode node = (JsonNode)nd;
			if (node.isNumber()) { 
				return node.asDouble();
			}
		} else if (nd instanceof Double) {
			return (Double)nd;
		} else if (nd instanceof Float) {
			return (Float)nd;
		} else if (nd instanceof Integer) {
			return (Integer)nd;
		} else if (nd instanceof Long) {
			return (Long)nd;
		}

		try {
			return Double.valueOf(nd.toString());
		} catch (Exception e) {
			return defaultvalue;
		}
	}
	
	public boolean getBoolean(String path, boolean defaultvalue) {
		Object nd = getPath(path);
		if (nd instanceof JsonNode) {
			JsonNode node = (JsonNode)nd;
			if (node.isBoolean()) {
				return node.asBoolean();
			}
		} else if (nd instanceof Boolean) {
			return (Boolean)nd;
		}
		return defaultvalue;
	}

	public String getString(String path, String defaultvalue) {
		Object nd = getPath(path);
		if (nd instanceof JsonNode) {
			JsonNode node = (JsonNode)nd;
			return (node.isValueNode() && !node.isNull()) ? node.asText() : defaultvalue;
		} else {
			return 	(null == nd) ? defaultvalue : String.valueOf(nd);
		}
	}

	public JsonBean removePath(String path) {
		StringBuilder sb = new StringBuilder();
		Map<String, Object> map = getParentMap(path, sb);
		if (map != null) {
			map.remove(sb.toString());
		}
		return this;
	}

	@SuppressWarnings("unchecked")
	public JsonBean setPath(String path, Object val) {
		Object nd = _map;
		Map<String, Object> map;
		String[] pathA = StringUtil.split(path, ".");//path.split("[\\[\\.]");
		for (int i=0; nd instanceof Map && i<pathA.length-1; ++i) {
			if (pathA[i].endsWith("]")) {
				pathA[i] = pathA[i].substring(0, pathA[i].length()-1);
			}
			
			map = (Map<String, Object>)nd;
			nd = map.get(pathA[i]);
			if (null == nd) {
				nd = new LinkedHashMap<String, Object>();
				map.put(pathA[i], nd);
			}
		}
		if (nd instanceof Map) {
			map = (Map<String, Object>)nd;
			if (val instanceof JsonBean) {
				map.put(pathA[pathA.length-1], ((JsonBean)val)._map);
			} else {
				map.put(pathA[pathA.length-1], val);
			}
		}
		return this;
	}

	@Override
	public boolean isEmpty() {
		return _map.isEmpty();
	}

	@Override
	public boolean containsKey(Object key) {
		if (!(key instanceof String)) {
			return false;
		}
		String path = (String)key;
		StringBuilder sb = new StringBuilder();
		Map<String, Object> map = getParentMap(path, sb);
		if (map != null) {
			return map.containsKey(sb.toString());
		}
		return false;
	}

	@Override
	public boolean containsValue(Object value) {
		return _map.containsValue(value);
	}

	@Override
	public Object get(Object key) {
		return _map.get(key);
	}

	@Override
	public Object put(String key, Object value) {
		return _map.put(key, value instanceof JsonBean ? ((JsonBean)value)._map : value);
	}

	@Override
	public Object remove(Object key) {
		return _map.remove(key);
	}

	@Override
	public void putAll(Map<? extends String, ? extends Object> m) {
		_map.putAll(m);
	}

	@Override
	public void clear() {
		_map.clear();
	}

	@Override
	public Set<String> keySet() {
		return _map.keySet();
	}

	@Override
	public Collection<Object> values() {
		return _map.values();
	}

	@Override
	public Set<java.util.Map.Entry<String, Object>> entrySet() {
		return _map.entrySet();
	}

	/*public String getStoreType() {
		return storeType;
	}

	public void setStoreType(String storeType) {
		this.storeType = storeType;
	}*/
	
	public boolean save(SQLRunner dao, JsonBean config, List<Object> others) {
		return save(dao, config, "this", others);
	}
	
	/*public boolean load(SQLRunner dao, JsonBean config, boolean loadchild) {
		return load(dao, config, null, loadchild);
	}*/

	@SuppressWarnings("unchecked")
	public dataState getDataState() {
		Object original = getOriginal();
		if (null == original) {
			return dataState.New;
		}
		String orgs = new JsonBean((Map<String, Object>)original).toString();
		String nows = toDataString();
		boolean iscomp = orgs.equals(nows);
		/*if (!iscomp) {
			System.out.println("orgs:"+orgs);
			System.out.println("nows:"+nows);
		}*/
		return iscomp ? dataState.NotModify : dataState.Modified;
	}

	public void setDataState(dataState newstate) {
		switch(newstate) {
		case New:
			setOriginal(null);
			break;
		case NotModify:
			setOriginal(new JsonBean(toDataString()));
			break;
		default: //Modified
			JsonBean ori = new JsonBean(toDataString());
			ori.put(ignore, StringUtil.getUUID()); //制造一点小变化
			setOriginal(ori);
		}
	}
	
	/* 结构化定义
	{
	  表名(table) 物理表名
	  对象名(object) 表字段的统一前缀
	  属性(props) {
	    属性(name): {
	      是否被忽略(temporary)
	      属性名(name)
	      是否可为空(nullable) 默认可空
	      是否主属性(key) 默认非主属性
	      属性类型(type) 子表(object)|json对象(json)|java的内置对象，如java.lang.Integer
	      属性长度(len)(转换成字符串的长度)
	    }
	  }
	}*/
	@SuppressWarnings("unchecked")
	private boolean save(SQLRunner dao, JsonBean cfg, String name, List<Object> addwheres) {
		dataState dsts = getDataState();
		if (dataState.NotModify == dsts) return true; //数据未被修改过，立即返回

		JsonBean config = cfg.getBean(name);
		if (null == config) return false;
		
		String table = config.getString("table", null);
		if (null == table) return false;
		
		String prefix = config.getString("object", "");
		if (!StringUtil.isEmpty(prefix)) {
			prefix += "_";
		}
		
		JsonBean propsmap = config.getBean("props");
		if (null == propsmap) return false;
		
		List<Object> wheres = new ArrayList<Object>();
		Map<String, Object> values = new HashMap<String, Object>();
		Iterator<Entry<String, Object>> it = getIterator("");
		while (it.hasNext()) {
			Entry<String, Object> item = it.next();
			String col = item.getKey();
			if (sysmark.equals(col) || ignore.equals(col)) continue; //系统数据不保存
			
			Object val = item.getValue();
			
			JsonBean def = propsmap.getBean(col);
			String coltype = "java.lang.String";
			boolean nosave = false;
			boolean iskey = false;
			
			if (def != null) {
				iskey = def.getBoolean("key", false);
				nosave = def.getBoolean("nosave", false);
				coltype = def.getString("type", "java.lang.String");
			}
			
			if ("list".equals(coltype)) {
				//子表
				JsonBean lst = new JsonBean((Map<String, Object>)val);
				Iterator<Entry<String, Object>> cit = lst.getIterator("");
				while(cit.hasNext()) {
					Entry<String, Object> citem = cit.next();
					JsonBean child = new JsonBean((Map<String, Object>)citem.getValue());
					if (!child.save(dao, cfg, col, null)) {
						return false;
					}
				}
			}

			if (nosave) continue;
			
			if (val instanceof Map) {
				val = (new JsonBean((Map<String, Object>)val)).toString();
			}
			
			if (iskey && dataState.Modified == dsts) {
				wheres.add(prefix+col);
				wheres.add("=");
				wheres.add(getPath(sysmark+".original."+col)); //要用数据原始值，否则当主键变化时无法保存
			}
			values.put(prefix+col, val);
		}
		
		//附加的where条件
		if (addwheres != null && addwheres.size()>0) {
			wheres.addAll(addwheres);
		}

		int row = 0;
		if (dataState.Modified == dsts) {
			row = new SQLMap().UPDATE(table)
				.VALUESMAP(values)
				.WHERE(wheres.toArray())
				.update(dao);
		} else {
			row = new SQLMap().INSERT(table)
				.VALUESMAP(values)
				.update(dao);
		}
		boolean ok = (1 == row);
		if (ok) {
			setOriginal(new JsonBean(toDataString()));
		}
		return ok;
	}
	
	/*
	 * 列初始化
	 * 1、去掉指定前缀
	 * 2、Clob->String
	 */
	private Map<String, Object> initColValue(Map<String, Object> map, String prefix) {
		int len = prefix.length();
		Map<String, Object> newmap = new LinkedHashMap<String, Object>(map.size());
		Iterator<Entry<String, Object>> it = map.entrySet().iterator();
		while(it.hasNext()) {
			Entry<String, Object> item = it.next();
			String key = item.getKey();
			Object val = item.getValue();
			if (val instanceof Clob) {
				Clob cval = (Clob)val;
				try {
					val = cval.getSubString(1, (int)cval.length());
				} catch (Exception e) {
					LogHelper.error(e);
					val = e.toString();
				}
			}
			newmap.put(key.startsWith(prefix) ? key.substring(len) : key, val);
		}
		return newmap;
	}
	
	/*
	 * 将map中指定字段转换成json对象
	 */
	private Map<String, Object> col2Json(Map<String, Object> dat, String[] cols) {
		for(int i=0; i<cols.length; ++i) {
			String col = cols[i];
			Object colval = dat.get(col);

			String sval = null;
			try {
				if (colval instanceof String) {
					sval = colval.toString();
				} else if (colval instanceof Clob) {
					Clob cval = (Clob)colval;
					sval = cval.getSubString(1, (int)cval.length());
				} else if (colval != null) {
					LogHelper.error("无法转换json:"+StringUtil.stringify(colval, null));
					continue;
				}
				JsonBean jb = new JsonBean(sval);
				dat.put(col, jb._map);
			} catch (Exception e) {
				LogHelper.error(e);
			}
		}
		return dat;
	}
	
	@SuppressWarnings("unchecked")
	public boolean load(SQLRunner dao, JsonBean cfg, Object[] wheres, boolean loadchild) {
		JsonBean config = cfg.getBean("this");
		if (null == config) return false;
		
		String table = config.getString("table", null);
		if (null == table) return false;
		
		String prefix = config.getString("object", "");
		if (!StringUtil.isEmpty(prefix)) {
			prefix += "_";
		}
		
		JsonBean propsmap = config.getBean("props");
		if (null == propsmap) return false;
		
		List<Object> wherelst = new ArrayList<Object>();
		List<String> jsoncols = new ArrayList<String>();
		Iterator<Entry<String, Object>> it = propsmap.getIterator("");
		while(it.hasNext()) {
			Entry<String, Object> item = it.next();
			String col = item.getKey();

			JsonBean def = new JsonBean((Map<String, Object>)item.getValue());
			if (null == wheres) {
				boolean iskey = def.getBoolean("key", false);
				if (iskey) {
					wherelst.add(prefix+col);
					wherelst.add("=");
					wherelst.add(get(col));
				}
			}
			
			String coltype = def.getString("type", null);
			if ("json".equals(coltype)) {
				jsoncols.add(col);
			}
		}
		if (null == wheres) {
			wheres = wherelst.toArray();
		}
		
		SQLMap sm = new SQLMap();
		Map<String, Object> ret = sm.FROM(table)
			.WHERE(wheres)
			.queryOne(dao);
		if (null == ret) return false;

		ret = col2Json(initColValue(ret, prefix), jsoncols.toArray(new String[0]));
	
		if (loadchild) { //要加载子项
			JsonBean obj = new JsonBean(ret);
			if (!obj.loadChild(dao, cfg, "this")) return false;
			ret = obj._map;
		}
		
		_map = ret;
		setOriginal(new JsonBean(toDataString()));

		return true;
	}
	
	/*
	 * 返回用于生成子json在父json中的键的字段列表
	 * 方法 将子json的主键，排除父json的主键后自然连接起来
	 */
	@SuppressWarnings("unchecked")
	private List<String> getChildBeanKeyList(JsonBean config) {
		List<String> keys = new ArrayList<String>();
		Iterator<Entry<String, Object>> it = config.getIterator("props");
		while(it.hasNext()) {
			Entry<String, Object> item = it.next();
			String col = item.getKey();

			JsonBean def = new JsonBean((Map<String, Object>)item.getValue());
			boolean iskey = def.getBoolean("key", false);
			boolean isparentkey = def.getBoolean("parentkey", false);
			if (iskey && !isparentkey) { //主键且非父对象主键的引用
				keys.add(col);
			}
		}
		return keys;
	}
	
	@SuppressWarnings("unchecked")
	private List<String> getJsonColList(JsonBean config) {
		List<String> cols = new ArrayList<String>();
		Iterator<Entry<String, Object>> it = config.getIterator("props");
		while(it.hasNext()) {
			Entry<String, Object> item = it.next();
			String col = item.getKey();

			JsonBean def = new JsonBean((Map<String, Object>)item.getValue());
			String coltype = def.getString("type", null);
			if ("json".equals(coltype)) {
				cols.add(col);
			}
		}
		return cols;
	}
	
	/*
	 * 加载当前对象的子项
	 */
	@SuppressWarnings("unchecked")
	private boolean loadChild(SQLRunner dao, JsonBean cfg, String name) {
		JsonBean config = cfg.getBean(name);
		if (null == config) return false;
		
		SQLMap sm = new SQLMap();
		List<Object> wheres = new ArrayList<Object>();
		Iterator<Entry<String, Object>> it = config.getIterator("props");
		while(it.hasNext()) {
			Entry<String, Object> item = it.next();
			String col = item.getKey();

			JsonBean def = new JsonBean((Map<String, Object>)item.getValue());
			String coltype = def.getString("type", null);
			JsonBean querywheres = def.getBean("query");
			if (!"list".equals(coltype)) continue;
			
			JsonBean childconfig = cfg.getBean(col);
			if (null == childconfig) return false;
			
			String childtable = childconfig.getString("table", null);
			if (StringUtil.isEmpty(childtable)) return false;
			
			String childpfx = childconfig.getString("object", "");
			if (!StringUtil.isEmpty(childpfx)) {
				childpfx += "_";
			}
			
			String childorder = def.getString("order", null);
			if (!StringUtil.isEmpty(childorder) && !StringUtil.isEmpty(childpfx)) {
				childorder = childpfx+childorder;
			}
			
			for (Entry<String, Object> querycol : querywheres.entrySet()) {
				wheres.add(childpfx+querycol.getKey());
				wheres.add("=");
				wheres.add(get(querycol.getValue()));
			}
			
			List<Map<String, Object>> retlst = sm.FROM(childtable)
				.WHERE(wheres.toArray())
				.ORDERBY(childorder)
				.query(dao);
			if (null != retlst) {
				StringBuilder sb = new StringBuilder();
				List<String> keys = getChildBeanKeyList(childconfig);
				List<String> jsons = getJsonColList(childconfig);
				for (Map<String, Object> citem : retlst) {
					JsonBean cd = new JsonBean(col2Json(initColValue(citem, childpfx), jsons.toArray(new String[0])));
					if (!cd.loadChild(dao, cfg, col)) return false; //递归加载子项

					sb.setLength(0);
					for (String key : keys) {
						sb.append(cd.get(key).toString());
					}
					setPath(col+"."+sb.toString(), cd);
					
					cd.setOriginal(new JsonBean(cd.toDataString()));
				}
			}
		}

		return true;
	}

	@SuppressWarnings("unchecked")
	public Map<String, Object> getOriginal() {
		return (Map<String, Object>)getPath(sysmark+".original");
	}

	public void setOriginal(Map<String, Object> original) {
		setPath(sysmark+".original", original);
	}
}
